package net.gdface.cassdk;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.LinkedHashMap;

import com.sun.jna.NativeLibrary;

import net.gdface.cassdk.jna.FSFaceSDKLibrary;
import net.gdface.cassdk.jna.THFI_FacePos;
import net.gdface.utils.ResourcePool.IntResourcePool;

/**
 * CASSDK 算法(线程安全)的调用实现<br>
 * 参见"FSFaceSDK.h"
 * @author guyadong
 *
 */
public class JniBridge extends BaseJniBridge{
	/**
	 * 授权码长度不满足要求
	 */
	protected static final String INVALID_LICENSE_LEN = "invalid license code length";
	/**
	 * 授权码类型校验不通过
	 */
	protected static final String INVALID_LICENSE_CHKSUM = "invalida license check sum";
	/**
	 * 授权码验证不通过
	 */
	protected static final String FAIL_TO_VALIDATE_LICENSE = "fail to validate license";
	/**
	 * 动态库过期
	 */
	protected static final String DLL_TIMEOUT = "dynamic library timout";
	/**
	 * 动态库初始化通道数超过限制
	 */
	protected static final String CHANNEL_EXCEED_LIMIT = "channel number exceed limit ";
	/**
	 * 导入依赖动态库失败
	 */
	protected static final String FAIL_TO_LOAD_DEPENDENT_DLL = "fail to load dependent dynamic library";
	/**
	 * 加密狗验证失败
	 */
	protected static final String FAIL_TO_VALIDATE_SOFTDOG = "fail to validate softdog";
	protected static final FSFaceSDKLibrary fsfacesdkLibrary;
	private final static String licenseCode;

	static {
		// 修改检测和特征通道数的默认值为 1
		CasConfig.setChannelNumIfAbsent(1);
		String cassdkBin = CASSDK_HOME + "/bin";
		NativeLibrary.addSearchPath("FSFaceSDK", cassdkBin);
		fsfacesdkLibrary = FSFaceSDKLibrary.INSTANCE;
		licenseCode = CasConfig.getLicenseCode();
		if(null == licenseCode){
			throw new ExceptionInInitializerError(
					String.format("NOT DEFINED liecnse code for poll code\n %s",getPollCode()));
		}
		FEATURE_SIZE = fsfacesdkLibrary.FSSize();
		logger.info(String.format("FEATURE_SIZE=%d bytes", FEATURE_SIZE));
		// 从配置文件读取初始化通道数
		init();
		// add hook for release JNI resource on JVM shutdown
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				logger.info("release cassdk JNI resoure...");
				release();
				// 如果用 logger 输出不能显示,why?
				System.out.println("release cassdk JNI resoure finished");					
			}
		});
	}

	/**
	 * 人脸识别模块(THFaceImage和THFeature)初始化， 只需在应用程序初始化时调用一次
	 * 
	 * @throws ExceptionInInitializerError
	 * @see #initTHFaceImage(int, int, THFIParam)
	 * @see #initTHFeature(int)
	 * @return 初始化失败则抛出异常{@link ExceptionInInitializerError}
	 */
	private static void init() throws ExceptionInInitializerError {
		logger.info(String.format("CASSDK %s initializing(SDK正在初始化)",CasConfig.getVersion().version));
		logger.info(String.format("model initializing(模型数据初始化) MODE:%s",
				CasConfig.isGpuMode()?"GPU ID:"+CasConfig.getDeviceID():"CPU"));
		
		int featureChannelNum = CasConfig.getFeatureChannelNum();
		int detectChannelNum = CasConfig.getFeatureChannelNum();
		try{
			copyModelToUserDir();
			{
				logger.info(String.format("人脸检测通道: %d",detectChannelNum));
				logger.info(String.format("特征提取通道: %d",featureChannelNum));
				detectChannelPool = new IntResourcePool(detectChannelNum);
				featureChannelPool = new IntResourcePool(featureChannelNum);
				logger.info("detect module initializing(人脸检测模块初始化)");
			}
			{
				logger.info("feature module initializing(特征提取模块初始化)");
				int result = fsfacesdkLibrary.FSInit((short)detectChannelNum, null == licenseCode? null :ByteBuffer.wrap(licenseCode.getBytes()));
				if(result <=0 ){
					StringBuffer buffer = new StringBuffer("fail to initTHFeature,error code:");
					buffer.append(result);
					switch(result){
					case -1:{
						buffer.append("msg:").append("open file \"feadb.db*\" error");
						break;
					}
					case -2:{
						buffer.append("msg:").append("check  file \"feadb.db*\" error");
						break;
					}
					case -3:{
						buffer.append("msg:").append("read  file \"feadb.db*\" error");
						break;
					}
					default:
						if(result<=-90){
							switch(result)
							{
							case -91:
								buffer.append("msg:").append(INVALID_LICENSE_LEN);
								break;
							case -92:
								buffer.append("msg:").append(INVALID_LICENSE_CHKSUM);
								break;
							case -93:
								buffer.append("msg:").append(FAIL_TO_VALIDATE_LICENSE);
								break;
							case -94:
								buffer.append("msg:").append(DLL_TIMEOUT);
								break;
							case -95:
								buffer.append("msg:").append(CHANNEL_EXCEED_LIMIT);
								break;
							case -96:
								buffer.append("msg:").append(FAIL_TO_LOAD_DEPENDENT_DLL);
								break;
							case -97:
								buffer.append("msg:").append(FAIL_TO_VALIDATE_SOFTDOG);
								break;
							default:
								buffer.append("msg:").append(INVALID_LICENSE);
								break;
							}							
						}
						break;
					}
					throw new JniException(buffer.toString());
				}
			}
		}catch(Exception e){
			logger.info(String.format("%s:%s",e.getClass().getSimpleName(),e.getMessage()));
			throw new ExceptionInInitializerError(e);
		}
		logger.info("CASSDK initialized(SDK初始化结束)");
	}

	/**
	 * 释放所有资源<br>
	 * JVM结束时会自动调用此方法<br>
	 */
	private static void release(){
		fsfacesdkLibrary.FSRelease();
	}
	

	/**
	 * 对图像进行人脸检测
	 * @param imgData
	 *            图像数据,长度必须为 (width*height*3),否则抛出异常 {@link IllegalArgumentException}
	 * @param bpp
	 *            图像像素位数(8或24),否则抛出异常 {@link IllegalArgumentException}
	 * @param width
	 *            图像宽度
	 * @param height
	 *            图像高度
	 * @return 返回人脸人眼等位置信息数组(没有检测到人脸则返回长度为0的空数组)
	 */
	public static THFI_FacePos[] detectFace(byte[] imgData, int bpp, int width, int height){
		if(null == imgData){
			throw new NullPointerException("imgData must not be null");
		}
		if(bpp != 24){
			throw new IllegalArgumentException("must RGB24");
		}
		if(imgData.length != width*height*3){
			throw new NullPointerException("INVALID imageData");
		}
		short nChannelID = detectChannelPool.apply().shortValue();
		// logger.info(String.format("detectChannelD=%d", nChannelID));
		THFI_FacePos[] pfps=new THFI_FacePos[maxDetectFaceNum];
		try{
			int result = fsfacesdkLibrary.FSDetectFace(nChannelID, 
					ByteBuffer.wrap(imgData), 
					bpp, 
					width, 
					height, 
					pfps, 
					pfps.length, 
					sampleSize);
			if(result<0){
				StringBuffer buffer = new StringBuffer("fail to THFI_DetectFace,error code:");
				buffer.append(result);
				switch(result){
				case -1:{
					buffer.append("msg:").append(INVALID_CHANNEL);
					break;
				}
				case -2:{
					buffer.append("msg:").append(INVALID_IMAGE);
					break;
				}
				case -3:{
					buffer.append("msg:").append(INVALID_FPS);
					break;
				}
				default:{
					if(result<=-90){
						buffer.append("msg:").append(INVALID_LICENSE);
					}
					break;
				}					
				}
				throw new JniException(buffer.toString());
			}
			return Arrays.copyOf(pfps, result);
		}finally{
			detectChannelPool.free();
		}
	}

	/**
	 * 对人脸图像(RGB24)指定的位置进行特征提取
	 * @param imgData
	 *            图像数据，RGB24格式(注意不需要位图四字节对齐)，在内存中人脸是正的,<br>
	 *            长度必须为 (width*height*3),否则抛出异常 {@link IllegalArgumentException}
	 * @param width
	 *            图像宽度
	 * @param height
	 *            图像高度
	 * @param facePos
	 *            人脸位置信息,为{@code null}时抛出异常 {@link IllegalArgumentException}
	 * 
	 * @return 人脸特征数组(提取失败返回null)
	 */
	public static byte[] getFaceFeature(byte[] imgData, int width, int height, THFI_FacePos facePos){
		if(null == imgData){
			throw new NullPointerException("imgData must not be null");
		}
		if(imgData.length != width*height*3){
			throw new NullPointerException("INVALID imageData");
		}
		short nChannelID = featureChannelPool.apply().shortValue();
		// logger.info(String.format("featureChannelD=%d", nChannelID));
		ByteBuffer featureBuffer = ByteBuffer.allocate(FEATURE_SIZE);
		try{
			int result = fsfacesdkLibrary.FSExtract(nChannelID , ByteBuffer.wrap(imgData), width, height, 3, facePos, featureBuffer);
			if(result<0){
				StringBuffer buffer = new StringBuffer("fail to EF_Extract,error code:");
				buffer.append(result);
				switch(result){
				case -1:{
					buffer.append("msg:").append(INVALID_PBUF);
					break;
				}
				case -2:{
					buffer.append("msg:").append(INVALID_CHANNEL);
					break;
				}
				default:{
					if(result<=-90){
						buffer.append("msg:").append(INVALID_LICENSE);
					}
					break;
				}				
				}
				throw new JniException(buffer.toString());
			}
			return featureBuffer.array();
		}finally{
			featureChannelPool.free();
		}
	}

	/**
	 * 对图像(RGB24)中的一组人脸位置提取特征码<br>
	 * @param imgData
	 * @param width
	 * @param height
	 * @param facePosArray
	 *            要提取特征的人脸位置对象数组,为{@code null}时抛出异常 {@link IllegalArgumentException}
	 * 
	 * @see #getFaceFeature(byte[], int, int, FacePos)
	 * @return 返回facePosArray,对于提取到特征码的元素，{@link FacePos#feature}包含特征码数据,<br>
	 *         特征提取失败则对应的 {@link FacePos#feature}为null
	 */
	public static byte[][] getFaceFeatures(byte[] imgData, int width, int height, THFI_FacePos[] facePosArray){
		if(null == imgData ){
			throw new NullPointerException("imgData must not be null");
		}
		if(null == facePosArray){
			throw new NullPointerException("facePosArray must not be null");
		}
		if(0==facePosArray.length){
			// 直接返回空数组,因为JNA不允许传入长度为0的 Structure array
			return new byte[0][FEATURE_SIZE];
		}
		if(imgData.length != width*height*3){
			throw new NullPointerException("INVALID imageData");
		}
		short nChannelID = featureChannelPool.apply().shortValue();
		// logger.info(String.format("featureChannelD=%d", nChannelID));
		ByteBuffer featuresBuffer = ByteBuffer.allocate(FEATURE_SIZE*facePosArray.length);
		try{
			int result = fsfacesdkLibrary.FSExtract_M(nChannelID , 
					ByteBuffer.wrap(imgData), 
					width, 
					height, 
					3, 
					facePosArray, 
					featuresBuffer, 
					facePosArray.length);
			if(result<0){
				StringBuffer buffer = new StringBuffer("fail to EF_Extract,error code:");
				buffer.append(result);
				switch(result){
				case -99:{
					buffer.append("msg:").append(INVALID_LICENSE);
					break;
				}
				case -1:{
					buffer.append("msg:").append(INVALID_PBUF);
					break;
				}
				case -2:{
					buffer.append("msg:").append(INVALID_CHANNEL);
					break;
				}
				default:
				}
				throw new JniException(buffer.toString());
			}
			byte[][] features = new byte[facePosArray.length][FEATURE_SIZE];
			for(int i = 0 ;i< facePosArray.length; ++i){
				features[i] = Arrays.copyOfRange(featuresBuffer.array(),i*FEATURE_SIZE,(i + 1)*FEATURE_SIZE);
			}
			return features;
		}finally{
			featureChannelPool.free();
		}
	}
	/**
	 * 对图像(RGB24)进行人脸检测并提取每个检测到的人脸特征码
	 * @param imgData
	 * @param width
	 * @param height
	 * @return 返回包含特征码数据的 {@link FacePos}对象数组(只包含成功提取特征码的位置对象)
	 * @see #detectFace(byte[], int, int, int, int)
	 * @see #getFaceFeatures(byte[], int, int, THFI_FacePos[])
	 */
	public static LinkedHashMap<THFI_FacePos,byte[]> detectAndGetFeatures(byte[] imgData, int width, int height){
		LinkedHashMap<THFI_FacePos, byte[]> map = new LinkedHashMap<THFI_FacePos,byte[]>(16);
		THFI_FacePos[] facepos = detectFace(imgData,24,width,height);
		byte[][] features = getFaceFeatures(imgData, width, height, facepos);
		for(int i = 0 ;i< facepos.length ; ++i){
			map.put(facepos[i], features[i]);
		}
		return map;		
	}
	/**
	 * 人脸特征相似度比对<br>
	 * 特征码为{@code null}或长度不等于 {@link #FEATURE_SIZE}则抛出{@link IllegalArgumentException}异常
	 * @param feature1
	 *            待比对的人脸特征
	 * @param feature2
	 *            待比对的人脸特征
	 * @return 两个人脸的相似度,范围在0-1之间,相似度值越高，表示比对的两个人脸越可能为同一个人
	 */
	public static float compareFeature(byte[] feature1, byte[] feature2){
		if(null == feature1 || null == feature2){
			throw new NullPointerException("feature1,feature2 must not be null");
		}
		if(feature1.length != FEATURE_SIZE || feature2.length != FEATURE_SIZE){
			throw new IllegalArgumentException("INVALID length of feature1 or feature2");
		}
		return fsfacesdkLibrary.FSCompare(ByteBuffer.wrap(feature1), ByteBuffer.wrap(feature2));
	}
	public static String getPollCode(){
		byte[] pollCode = new byte[128];
		int result = fsfacesdkLibrary.FSRegist(ByteBuffer.wrap(pollCode));
		if(result <0){
			throw new JniException(String.format("fail to get poll code,error: %d", result));
		}
		return new String(Arrays.copyOf(pollCode, result));
	}
}
